useState 管理條件、即時過濾 viewsrc/components/Projects.tsximport React, { useState, useMemo } from 'react'
import type { Project } from '../data/projects'
type Props = {
  items: Project[]
}
export default function Projects({ items }: Props) {
  const [onlyFeatured, setOnlyFeatured] = useState(false)
  const [keyword, setKeyword] = useState('')
  // 過濾邏輯
  const view = useMemo(() => {
    const kw = keyword.toLowerCase()
    return items.filter(p => {
      const matchFeatured = !onlyFeatured || p.featured
      const matchKeyword =
        !kw ||
        p.title.toLowerCase().includes(kw) ||
        p.tech.toLowerCase().includes(kw) ||
        p.desc.toLowerCase().includes(kw)
      return matchFeatured && matchKeyword
    })
  }, [items, onlyFeatured, keyword])
  return (
    <section id="projects" className="container section">
      <h2>作品集 Projects</h2>
      <div style={{ margin: '12px 0', display: 'flex', gap: '12px', alignItems: 'center' }}>
        <label>
          <inputtype="checkbox"
            checked={onlyFeatured}
            onChange={(e) => setOnlyFeatured(e.target.checked)}
          />{' '}
          只看精選
        </label>
        <inputtype="text"
          value={keyword}
          onChange={(e) => setKeyword(e.target.value)}
          placeholder="搜尋關鍵字"
          style={{ flex: 1, maxWidth: '260px' }}
        />
        <small className="muted">{view.length} / {items.length}</small>
      </div>
      <div className="project-grid">
        {view.map((p) => (
          <article className="card" key={p.id}>
            <h3>{p.title}</h3>
            <p className="muted">{p.tech}</p>
            <p>{p.desc}</p>
            <div style={{ display: 'flex', gap: '8px', marginTop: '8px' }}>
              {p.demo && <a className="btn small" href={p.demo} target="_blank">Demo</a>}
              <a className="btn small btn-outline" href={p.repo} target="_blank">GitHub</a>
            </div>
          </article>
        ))}
      </div>
    </section>
  )
}
featured: true 的項目.vue 元件開發體驗佳,資料綁定與響應式很直覺到這裡,三個框架我們都用來實作了 同一個履歷網站,這有幾個意義:
Props,避免後續維護困難。恭喜!30 天你完成了: